#include "noiseUntil.hpp"
#include "../math/random_generator.hpp"
#include <utility>

void NoiseUntil::initPerlinNoise() {
  mt_uniform_float_rg rg( 0.f, 1.f );

  // init gradient vector table a unit cube
  grad[0] = vec3f( 1, 1, 0 );
  grad[1] = vec3f( -1, 1, 0 );
  grad[2] = vec3f( 1, -1, 0 );
  grad[3] = vec3f( -1, -1, 0 );

  grad[4] = vec3f( 1, 0, 1 );
  grad[5] = vec3f( -1, 0, 1 );
  grad[6] = vec3f( 1, 0, -1 );
  grad[7] = vec3f( -1, 0, -1 );

  grad[8] = vec3f( 0, 1, 1 );
  grad[9] = vec3f( 0, -1, 1 );
  grad[10] = vec3f( 0, 1, -1 );
  grad[11] = vec3f( 0, -1, -1 );

  grad[12] = vec3f( 1, 1, 0 );
  grad[13] = vec3f( -1, 1, 0 );
  grad[14] = vec3f( 0, -1, 1 );
  grad[15] = vec3f( 0, -1, -1 );

  // 0 ~ N - 1 random integer
  for ( i32 i = 0; i < N; i++ ) {
    phi[i] = i;
  }
  // shuffle
  for ( i32 i = N - 2; i >= 0; i-- ) {
    i32 target = i32( rg() * i );
    std::swap( phi[i + 1], target );
  }
}

f32 NoiseUntil::perlinNoise( const vec3f &p ) const {
  i32 fi, fj, fk;
  f32 fu, fv, fw, sum;
  vec3f v;

  fi = i32( floor( p.x ) );
  fj = i32( floor( p.y ) );
  fk = i32( floor( p.z ) );

  fu = p.x - fi;
  fv = p.y - fj;
  fw = p.z - fk;

  sum = 0.f;

  v = vec3f( fu, fv, fw );
  sum += knot( fi, fj, fk, v );

  v = vec3f( fu - 1, fv, fw );
  sum += knot( fi + 1, fj, fk, v );

  v = vec3f( fu, fv - 1, fw );
  sum += knot( fi, fj + 1, fk, v );

  v = vec3f( fu, fv, fw - 1 );
  sum += knot( fi, fj, fk + 1, v );

  v = vec3f( fu - 1, fv, fw - 1 );
  sum += knot( fi + 1, fj, fk + 1, v );

  v = vec3f( fu - 1, fv - 1, fw );
  sum += knot( fi + 1, fj + 1, fk, v );

  v = vec3f( fu, fv - 1, fw - 1 );
  sum += knot( fi, fj + 1, fk + 1, v );

  v = vec3f( fu - 1, fv - 1, fw - 1 );
  sum += knot( fi + 1, fj + 1, fk + 1, v );

  return sum;
}

f32 NoiseUntil::turbulence( const vec3f &p, i32 depth ) const {
  f32 sum = 1.f;
  f32 weight = 1.f;
  vec3f ptemp = p;
  sum = fabs( perlinNoise( ptemp ) );

  for ( i32 i = 1; i < depth; i++ ) {
    weight *= 2;
    ptemp = p * weight;
    sum += fabs( perlinNoise( ptemp ) ) / weight;
  }

  return sum;
}

f32 NoiseUntil::dturbulence( const vec3f &p, i32 depth, f32 d ) const {
  f32 sum = 0.f;
  f32 weight = 1.f;
  vec3f ptemp = p;
  sum += fabs( perlinNoise( ptemp ) ) / d;

  for ( i32 i = 1; i < depth; i++ ) {
    weight *= d;
    ptemp = p * weight;
    sum += fabs( perlinNoise( ptemp ) ) / d;
  }
  return sum;
}

NoiseUntil::NoiseUntil() { initPerlinNoise(); }